이 노트북은 Hell, Tensorflow! 의 내용을 참고로 하여 노트북으로 재 정돈한 것입니다.


In [1]:
%load_ext watermark
%watermark -vm -p tensorflow,numpy,scikit-learn


CPython 3.5.1
IPython 4.2.0

tensorflow 0.9.0
numpy 1.11.0
scikit-learn 0.17.1

compiler   : GCC 4.2.1 (Apple Inc. build 5577)
system     : Darwin
release    : 15.5.0
machine    : x86_64
processor  : i386
CPU cores  : 8
interpreter: 64bit

In [2]:
import tensorflow as tf

텐서플로우의 디폴트 그래프는 직접 접근을 할 수 없고 get_default_graph 메소드를 이용합니다.


In [3]:
graph = tf.get_default_graph()

초기에는 디폴트 그래프에 아무런 연산도 들어 있지 않고 비어 있습니다.


In [4]:
graph.get_operations()


Out[4]:
[]

실수 1.0 값을 가지는 상수 input_value 를 만듭니다. name 옵션을 사용하여 이름을 지정하면 텐서보드의 그래프에서 구분하기 편리합니다.


In [5]:
input_value = tf.constant(1.0, name='input_value')

상수 하나도 텐서플로우 그래프에서는 하나의 노드로 취급되어 get_operations 에서 리턴되는 리스트가 비어있지 않습니다.


In [6]:
graph.get_operations()


Out[6]:
[<tensorflow.python.framework.ops.Operation at 0x112b66ef0>]

get_operations 로 받은 리스트에는 하나의 엘리먼트가 들어 있고 엘리먼트는 Operation 클래스의 인스턴스입니다.


In [7]:
ops = graph.get_operations()
len(ops), ops[0].__class__


Out[7]:
(1, tensorflow.python.framework.ops.Operation)

ops 의 첫번째 노드(여기서는 상수노드)의 정의를 조회하면 프로토콜 버퍼 형식으로 연산 노드를 표현하고 있음을 알 수 있습니다.


In [8]:
op = ops[0]
op.node_def


Out[8]:
name: "input_value"
op: "Const"
attr {
  key: "dtype"
  value {
    type: DT_FLOAT
  }
}
attr {
  key: "value"
  value {
    tensor {
      dtype: DT_FLOAT
      tensor_shape {
      }
      float_val: 1.0
    }
  }
}

input_value는 상수 텐서를 위한 일종의 연산 노드이며 값이 들어 있지 않습니다.


In [9]:
input_value.__class__, input_value


Out[9]:
(tensorflow.python.framework.ops.Tensor,
 <tf.Tensor 'input_value:0' shape=() dtype=float32>)

텐서플로우의 세션을 생성한 후에 input_value 를 실행하면 결과 값이 리턴됩니다.


In [10]:
sess = tf.Session()
sess.run(input_value)


Out[10]:
1.0

두 수를 곱하는 연산을 만들어 보기 위해 weight 변수를 만듭니다. 상수 노드는 tensorflow.python.framework.ops.Tensor 의 객체인데 변수 노드는 tensorflow.python.ops.variables.Variable 의 객체입니다.


In [11]:
weight = tf.Variable(0.8, name='weight')
weight


Out[11]:
<tensorflow.python.ops.variables.Variable at 0x112b75668>

이제 연산 노드는 다섯 개로 늘어납니다. 변수 텐서를 생성하면 그래프에 초기값과 할당, 조회에 관련된 노드가 추가로 생성되기 때문입니다.


In [12]:
ops = graph.get_operations()
for op in ops:
    print(op.name)


input_value
weight/initial_value
weight
weight/Assign
weight/read

weight 변수와 input_value 상수를 곱하여 곱셈 노드로 만들어지는 output_value 텐서를 만듭니다.


In [13]:
output_value = weight * input_value
output_value


Out[13]:
<tf.Tensor 'mul:0' shape=() dtype=float32>

그래프의 노드를 다시 조회하면 mul 노드가 추가된 것을 확인할 수 있습니다.


In [14]:
ops = graph.get_operations()
for op in ops:
    print(op.name)


input_value
weight/initial_value
weight
weight/Assign
weight/read
mul

그래프의 모든 변수를 초기화하기 위해 init 노드를 만들고 run 메소드로 실행합니다.


In [15]:
init = tf.initialize_all_variables()
sess.run(init)

1 * 0.8 의 출력 값은 예상대로 0.8을 리턴합니다.


In [16]:
sess.run(output_value)


Out[16]:
0.80000001

SummaryWriter를 사용하여 log_simple_graph 디렉토리에 sess에서 만들어진 세션 그래프 정보를 기록합니다.


In [17]:
summary_writer = tf.train.SummaryWriter('log_simple_graph', sess.graph)

쉘에서 tensorboard --logdir=log_simple_graph 명령으로 텐서보드를 실행하고 브라우저로 http://localhost:6006 으로 접속한 후 그래프 탭을 클릭하면 아래와 같은 그림이 보입니다. 이 그래프는 init 연산에서 weight 변수를 초기화하고 mul 연산에서 input_valuewegiht 를 사용하고 있다는 것을 잘 보여 주고 있습니다.

같은 값을 가지는 상수와 변수를 x, w 로 다시 만듭니다. 그리고 곱셉 노드를 파이썬의 곱셉 연산자를 사용하지 않고 텐서플로우에서 제공하는 수학 함수인 mul을 사용하여 표현할 수 있습니다.


In [18]:
x = tf.constant(1.0, name='x')
w = tf.Variable(0.8, name='w')
y = tf.mul(w, x, name='y')

In [19]:
init = tf.initialize_all_variables()
sess.run(init)

실제 참 값(y_)을 0이라고 정의하고 예측값과의 차이의 제곱을 에러 함수(loss function or error function)로 정의합니다.


In [20]:
y_ = tf.constant(0.0)
loss = (y - y_)**2

학습속도를 0.025로 하고 그래디언트 디센트 최적화 방식을 선택합니다.


In [21]:
optim = tf.train.GradientDescentOptimizer(learning_rate=0.025)
grads_and_vars = optim.compute_gradients(loss)

In [22]:
grads_and_vars


Out[22]:
[(None, <tensorflow.python.ops.variables.Variable at 0x112b75668>),
 (<tf.Tensor 'gradients/y_grad/tuple/control_dependency:0' shape=() dtype=float32>,
  <tensorflow.python.ops.variables.Variable at 0x112b8ab38>)]

수동으로 에러함수의 기울기를 계산해 보면 아래와 같이 1.6이 나옵니다. 가중치 값이 0.8 이므로 y = 0.8 * 1.0 = 0.8 이 되고 위에서 가정한 대로 y_ = 0 입니다. 에러 함수의 미분 방정식은 2(y - y_) 이므로 결과 값은 2 * 0.8 = 1.6 이 됩니다.


In [23]:
sess.run(grads_and_vars[1][0])


Out[23]:
1.6

계산된 그래디언트를 가중치에 반영하면 학습속도가 0.025 이므로 0.025 * 1.6 = 0.04 가 되어 w 가 0.04 만큼 감소됩니다.


In [24]:
sess.run(optim.apply_gradients(grads_and_vars))

In [25]:
sess.run(w)


Out[25]:
0.75999999

이 과정을 반복하도록 루프를 작성하고 summary_writer를 이용하여 결과 y 값을 기록하면 텐서보드에서 그래프로 값의 변화를 확인할 수 있습니다.


In [26]:
train_step = tf.train.GradientDescentOptimizer(0.025).minimize(loss)
summary_y = tf.scalar_summary('output', y)
for i in range(100):
    summary_str = sess.run(summary_y)
    summary_writer.add_summary(summary_str, i)
    sess.run(train_step)

결과 그래프에서 예측 값 y 가 0 으로 수렴하고 있는 과정을 볼 수 있습니다.